/***************************************************************************
 *   Copyright (C) 2006 by Sassan Panahinejad				   *
 *   ico2ico2@gmail.com							   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/loop.h>
#include <sys/mount.h>
#include "loop.h"
#include "error.h"


int set_loop(char *device, char filename[], unsigned long long offset, unsigned long long sizelimit)
{
	int loopfd, filefd, ret;
	struct loop_info64 li;
	loopfd = open(device, 0);
	if (loopfd <= 0) {
		info_output("Error opening device %s: %m\n", device);
		return -1;
	}
	filefd = open(filename, 0);
	if (filefd < 0) {
		info_output("Error opening file: %s: %m\n", filename);
		close(loopfd);
		return -1;
	}
	ret = ioctl(loopfd, LOOP_SET_FD, filefd);
	if (ret < 0) {
		info_output("Error setting  up loop device: ioctl: %s: LO_SET_FD: %m\n", device);
		close(loopfd);
		close(filefd);
		return -1;
	}
	memset(&li, 0, sizeof(struct loop_info));
	li.lo_flags = LO_FLAGS_READ_ONLY;
	realpath(filename, li.lo_file_name);
	li.lo_offset = offset;
	li.lo_sizelimit = sizelimit;
	ret = ioctl(loopfd, LOOP_SET_STATUS64, &li);
	if(ret < 0) {
		info_output("ioctl failed on %s: LO_SET_STATUS: %m\n", device);
		close(loopfd);
		close(filefd);
		return -1;
	}
	close(loopfd);
	close(filefd);
	return 0;
}

int find_unused_loopno(char *template)
{
	int i, loopfd;
	char *loopfile;
	struct loop_info li;
	loopfile = malloc(strlen(template)+1);
	for(i = 0; i < MAX_LOOP; i++) {
		sprintf(loopfile, template, i);
		loopfd = open(loopfile, O_RDONLY);
		if (loopfd <= 0) 
			continue;
		if (ioctl(loopfd, LOOP_GET_STATUS, &li) == 0) {
			close(loopfd);
			continue;
		}
		else if(errno == ENXIO) {
			close(loopfd);
			return i;
		}
	}
	info_output("Cannot find unused loop\n");
	free(loopfile);
	return -1;
}

char *find_unused_loop(char *template)
{
	char *loopfile, *result;
	int loopno;
	loopno = find_unused_loopno(template);
	if (loopno < 0)
		return NULL;
	loopfile = malloc(strlen(template)+1);
	sprintf(loopfile, template, loopno);
	return loopfile;
}

int del_loop(char *device)
{
	int ret, fd;
	fd = open(device, O_RDONLY);
	if (fd < 0) {
		info_output("Failed to open %s: %m\n", device);
		return fd;
	}
	ret = ioctl(fd, LOOP_CLR_FD, 0);
	if (ret < 0)
		info_output("Clearing loop device %s failed: %m\n", device);
	return ret;
}

int query_loop(char *device, struct loop_info *li)
{
	int ret, fd;
	fd = open(device, O_RDONLY);
	if (fd < 0) {
		info_output("Unable to open %s for reading\n", device);
		return fd;
	}
	ret = ioctl(fd, LOOP_GET_STATUS, li);
	if (ret < 0) {
		info_output("Unable to retrieve device information for %s: %m\n", device);
		return ret;
	}
	return 0;
}

int loop_get_filename(char *device, char **filename)
{
	struct loop_info li;
	int ret;
	ret = query_loop(device, &li);
	if (ret)
		return ret;
	*filename = strdup(li.lo_name);
	if (!*filename)
		return -1;
	return 0;
}
